/**
 * 
 */
package edu.serg.mbar.ucon.pdp;

import edu.serg.mbar.ucon.interfaces.AttributeReader;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.Vector;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.apache.log4j.Logger;

import edu.serg.mbar.ucon.interfaces.ExpressionEvaluator;
import edu.serg.mbar.ucon.interfaces.IFunction;
import edu.serg.utils.XMLUtils;

/**
 * 
 * This class processes the Expression node/element in the UCON policy
 * 
 * @author Sanaullah
 */
public class ExpressionEvaluationManager implements ExpressionEvaluator {

	static Logger log = Logger.getLogger(ExpressionEvaluationManager.class);
	private static AttributeReader attribReader ;
	
	public ExpressionEvaluationManager(AttributeReader atRdr)
	{
		attribReader=atRdr;
	}
	
	private ExpressionEvaluationManager(){}
	
	/**
	 * Evaluates the Expression element/node in the UCON policy xml file
	 * 
	 * @param expressionNode
	 * 		The DOM Node object of the UCON policy document
	 * @param subject
	 * 		The SubjectID of the Subject who has requested or is accessing the Object
	 * @param object
	 * 		The ObjectID of the Object being requested or accessed by the Subject
	 * @return
	 * 		The Vector of Objects which contains result or the values generated after processing the Expression
	 * 
	 */
	@Override
	public Vector<Object> evaluateExpression(Node expressionNode,
			String subject, String object) {
						
		long startTime=System.currentTimeMillis();
		String nameOfNode=expressionNode.getNodeName();
		Vector<Object> resultSet=new Vector<Object>();
		Vector<Object> attributeValue = new Vector<Object>();
		Object constantValue= new Object();
		Vector<Object> expressionResult=new Vector<Object>();
		Vector<Object> functionArgument = new Vector<Object>();
		log.info("Evaluating Expression");
		if (nameOfNode.split(":", 2)[1].equalsIgnoreCase("Expression"))
		{
			XMLUtils xmlUtils = new XMLUtils();
//			log.debug("------------------------------------------------------");
//			String xmlOfExpression = xmlUtils.xmlFromNodetoString(expressionNode);
//			log.debug(xmlOfExpression);
//			log.debug("------------------------------------------------------");
			log.debug("Start evaluating expression");
		
			try {
				String functionID=expressionNode.getAttributes().getNamedItem("FunctionID").getNodeValue();
			
				IFunction function = (IFunction) Class.forName(functionID).newInstance();
			
				log.info("FunctionID of the expression being evaluated is '"+ functionID +"'");
				//NodeList childNodes=expressionNode.getChildNodes();
				//get the child nodes of the the expression element/node
				NodeList childNodes = xmlUtils.xPathEvaluateToNodeList(expressionNode, "./*");
				log.debug("Child nodes within New expression Node are " + childNodes.getLength() );
			
				int totalChildNodes =childNodes.getLength();
				for (int i=0; i<totalChildNodes ; i++){
					String nodeName = childNodes.item(i).getNodeName();
					log.debug ("Element " + i + " is " + nodeName);
//					log.debug("     ?????");
//					log.debug(xmlUtils.xmlFromNodetoString(childNodes.item(i)));
//					log.debug("     ?????");
					
					// determine whether the Node Name contains a prefix or not
					if (nodeName.contains(":"))
					{
						//if contains prefix then remove the prefix by splitting the name into prefix and name and store the name in the variable
						nodeName= nodeName.split(":",2)[1];
					}
					// now based on the name of the node/Element take an appropriate action
					if ( nodeName.equalsIgnoreCase("ObjectAttribute")) 
					{
						String targetOfAttribute=attribReader.resolveTargetOfObjectAttributeNode(childNodes.item(i),subject,object);
						Node attributeNameNode =xmlUtils.xPathEvaluateToNode(childNodes.item(i), "./p:AttributeName");
						attributeValue=attribReader.getAttributeValue(targetOfAttribute, xmlUtils.getStringValueOfNode(attributeNameNode));
						functionArgument.addAll(attributeValue);
					} 
					else
					{
						if (nodeName.equalsIgnoreCase("Constant"))
						{
							NamedNodeMap attributesOfConstant=childNodes.item(i).getAttributes();
							//Determine the value of the Datatype attribute of the element Constant
							Node dataTypeOfConstant = attributesOfConstant.getNamedItem("DataType");
							log.debug("The datatype of the Constant is " + dataTypeOfConstant.getNodeValue());

							// if the Datatype of the Constant is Time then
							if ((String) dataTypeOfConstant.getNodeValue()=="Time")
							{
								SimpleDateFormat timeFormat =new SimpleDateFormat("HHmm");
								String timeString = xmlUtils.getStringValueOfNode(childNodes.item(i));
								log.debug("The value of the Constant = " + timeString );
								constantValue=timeFormat.parse(timeString);
							}
							else
							{
								constantValue =xmlUtils.getStringValueOfNode(childNodes.item(i));
								log.debug("The value of the constant = " + constantValue);
							}
							// add the value of the constant to the list of arguments which is to be passed to the function
							log.debug("Adding the value of the constant to the list of the arguments that are to be passed to the function");
							functionArgument.add(constantValue);
						}
						else
						{
							if (nodeName.equalsIgnoreCase("Expression"))
							{
								expressionResult=evaluateExpression(childNodes.item(i),subject,object);
								functionArgument.addAll(expressionResult);
							}
						}
					}
				}
				log.debug("* # Calling Function '" + functionID +"' with the vector of Size " + functionArgument.size()  +" # *");
				Object resultOfFunction =function.evaluate(functionArgument);
				resultSet.add(resultOfFunction);
			
			}catch (Exception e)
			{
				log.error(e.getMessage());
				e.printStackTrace();
		
			}
		}
		else
		{
			log.error("Invalid expression Node");
		}
		
		long finishTime=System.currentTimeMillis();
		SimpleDateFormat dateFormat =new SimpleDateFormat("HH:mm:ss.SSS");
		dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
		log.info("* --> * Elapsed Time: Expression Evaluated in [" + (dateFormat.format(new Date((finishTime -startTime)))) + "] Time");
				
		return resultSet;
		
	}

}
